import datetime
import math
from collections import defaultdict

from x_tw.db.in_service import GlobalTime, TaskDataService
from x_tw.db.out_service import VehicleTask, VehicleOrderDetail, WaveOrderSku
from x_tw.util.tools import parse_second, calculate_time_window


class DataProcessor:
    def __init__(self, context):
        self.context = context
        self.already_in_car = set()

    @staticmethod
    def get_time_data(windows_data):
        # 根据时间从早到晚排序
        time_data_list = []

        for windows in windows_data:
            plan_begin = datetime.datetime.strptime(windows['planBegin'].replace("T", " "), "%Y-%m-%d %H:%M:%S")
            plan_end = datetime.datetime.strptime(windows['planEnd'].replace("T", " "), "%Y-%m-%d %H:%M:%S")
            time_data_list.append([parse_second(plan_begin), parse_second(plan_end)])

        return time_data_list

    @staticmethod
    def get_vehicle_plan_data(vehicle_plan_list):
        plan_begin_time_map = defaultdict(list)
        plan_end_time_map = defaultdict(list)
        plan_car_num_map = defaultdict(list)

        for plan in vehicle_plan_list:
            deliver_factory = plan['deliverFactoryCode']
            plan_begin_time_map[deliver_factory].append(plan['planBegin'])
            plan_end_time_map[deliver_factory].append(plan['planEnd'])
            plan_car_num_map[deliver_factory].append(plan['vehicleNum'])
        return plan_begin_time_map, plan_end_time_map, plan_car_num_map

    @staticmethod
    def get_zz_and_floor_data(storer_list):
        zz_factory_code_list = []
        plan_floor_map = defaultdict(list)
        for data in storer_list:
            address2 = data['address2']
            if address2 in ["是", "否", "Y", "N", "null", " ", "", None]:
                address2 = '02'
            plan_floor_map[data['storerkey']] = address2
            if data['address1'] == 'ZZ':
                zz_factory_code_list.append(data['storerkey'])

        return plan_floor_map, zz_factory_code_list

    @staticmethod
    def get_windows_config_data(time_window_config_list):
        windows_config_map = defaultdict(list)
        deliver_factory_map = {}

        for time in time_window_config_list:
            deliver_factory = time['deliverFactoryCode']
            windows_config_map[deliver_factory].append(time)
            deliver_factory_map[deliver_factory] = time['deliverFactory']
        return windows_config_map, deliver_factory_map

    @staticmethod
    def get_detail_and_lpn_data(time_window_config_list, deliver_factory_map, zz_factory_code_list):
        # 初始化数据结构
        lpn_map = defaultdict(set)
        storer_map = defaultdict(set)
        detail_map = defaultdict(list)
        sku_map = defaultdict(list)
        detail_factory_code_map = {}
        is_continue = True
        response_message = ''

        # 遍历订单列表
        for item in time_window_config_list:
            storer_key = item['cCompany']
            order_type = item['consigneekey']

            # 遍历订单详情
            for detail in item['details']:
                detail_key = detail['orderDetailId']

                # 获取工厂信息，如果不存在则设置错误消息并跳出循环
                factory = deliver_factory_map.get(storer_key)

                # 存储交付工厂信息
                detail_factory_code_map[detail_key] = {'deliverFactoryCode': storer_key, 'deliverFactory': factory}

                # 根据plt_list或box_list添加sku到sku_map
                plt_list = detail['pltIdResultDTOS']
                box_list = detail['boxIdResultDTOS']
                if plt_list:
                    sku_map[detail_key].append(plt_list[0]['sku'])
                elif box_list:
                    sku_map[detail_key].append(box_list[0]['sku'])

                # 构建lpn_map的键值对
                if storer_key not in zz_factory_code_list:
                    key = f"{storer_key}_{order_type}" if order_type and order_type.strip() else storer_key
                else:
                    key = storer_key
                lpn_map[key].add(detail_key)
                storer_map[key] = storer_key
                # 添加订单详情到detail_map
                detail_map[detail_key].append(detail)

        # 如果循环被中断，不再继续执行后续操作
        if not is_continue:
            return None, None, None, None, is_continue, response_message

        # 返回处理后的数据结构和状态信息
        return lpn_map, storer_map, detail_map, sku_map, detail_factory_code_map, is_continue, response_message

    @staticmethod
    def to_arr_global_time(global_time: list[GlobalTime]):
        global_time_arr = []

        for global_time in global_time:
            global_time_arr.append([parse_second(global_time.begin_time), parse_second(global_time.end_time)])

        return global_time_arr

    @staticmethod
    def parse_task_data(car_list, data_list, job_type_total, job_list, total_sub_data, total_d_num,
                        total_d_goods, solution_bool_sub_job, solution_bool_c, solution_x_total, windows_data_arr,
                        detail_data, windows_data, zz_r, box_port_num,
                        vehicle_task_list, vehicle_order_detail_list,
                        wave_order_sku_list):

        for i, dlf in enumerate(car_list):
            for j, car in enumerate(dlf):
                for k, goods in enumerate(car):
                    # 获取货物类型列表
                    job_type = job_type_total[i][j][k]
                    # 如果自制有结束时间 取自制时间
                    solution_time = zz_r if zz_r is not None else solution_x_total

                    # 初始化变量
                    box_begin_time = box_end_time = pallet_begin_time = pallet_end_time = None
                    plan_box_begin_time = plan_box_end_time = plan_pallet_begin_time = plan_pallet_end_time = None
                    is_continue = False

                    # 根据job_type长度和solution_bool_sub_job的值计算时间窗口
                    for index, jt in enumerate(job_type):
                        if solution_bool_sub_job[i][j][k][index] == 1:
                            if jt == 1:
                                box_begin_time, box_end_time = calculate_time_window(solution_time[i][j][k][index])
                                plan_box_begin_time, plan_box_end_time = calculate_time_window(
                                    solution_x_total[i][j][k][index])
                            else:
                                pallet_begin_time, pallet_end_time = (
                                    calculate_time_window(solution_time[i][j][k][index]))
                                plan_pallet_begin_time, plan_pallet_end_time = (
                                    calculate_time_window(solution_x_total[i][j][k][index]))
                            is_continue = True

                    if not is_continue:
                        continue

                    # 计算装车开始时间跟结束时间
                    begin_obj = min(filter(None, [box_begin_time, pallet_begin_time, box_end_time, pallet_end_time,
                                                  plan_box_begin_time, plan_box_end_time,
                                                  plan_pallet_begin_time, plan_pallet_end_time]))
                    end_obj = max(filter(None, [box_begin_time, pallet_begin_time, box_end_time, pallet_end_time,
                                                plan_box_begin_time, plan_box_end_time,
                                                plan_pallet_begin_time, plan_pallet_end_time]))
                    # 初始化装车任务对象
                    vehicle_task = VehicleTask(begin_obj, end_obj, '', '', '', '',
                                               '', pallet_begin_time, pallet_end_time,
                                               box_begin_time, box_end_time)
                    # 获取开始跟结束时间的分钟数
                    begin_min = min(solution_time[i][j][k][index][0] for index in range(len(job_type)))
                    end_min = max(solution_time[i][j][k][index][1] for index in range(len(job_type)))
                    # 遍历每个时间段
                    # 计算B在A中每个元素中的长度，并记录下标
                    lengths = defaultdict(list)
                    # 初始化所有重叠时间
                    total_sum = 0
                    for index, data in enumerate(windows_data_arr):
                        # 计算重叠的开始时间和结束时间
                        overlap_start = max(begin_min, data[0])
                        overlap_end = min(end_min, data[1])

                        # 如果有重叠部分
                        if overlap_start < overlap_end:
                            # 将重叠时间与时间窗口下标关联起来
                            lengths[index] = overlap_end - overlap_start
                            total_sum += overlap_end - overlap_start
                    time_box_list, time_pallet_list = (DataProcessor.split_order_details
                                                       (job_list[i][j][k], total_d_num[i][j][k],
                                                        total_d_goods[i][j][k], total_sub_data, lengths, total_sum))
                    # 初始化 订单id集合
                    detail_id_set = set([])
                    # 初始化 时间窗配置字典
                    config = {}
                    # 处理time_box_list和time_pallet_list
                    first_sku = None
                    for time_list, is_box in [(time_box_list, True), (time_pallet_list, False)]:
                        for y, time_items in time_list.items():
                            for detail in time_items:
                                detail_id = detail['detail_id']
                                config = data_list.get(detail_id)
                                detail_id_set.add(detail_id)
                                wave_order_sku = DataProcessor.create_wave_order_sku(
                                    detail_data, detail, config, vehicle_task, windows_data, y, is_box)
                                if first_sku is None:
                                    first_sku = wave_order_sku.wops_delivery_time_windowid
                                if wave_order_sku:
                                    wave_order_sku_list.append(wave_order_sku)
                    for windows_datum in windows_data:
                        if first_sku == windows_datum.get("id"):
                            vehicle_task.work_date = windows_datum.get("workDate")
                            break
                    # 创建VehicleOrderDetail对象
                    vehicle_order_detail_list.extend(
                        [VehicleOrderDetail(task_id=vehicle_task.id, order_detail_id=detail_id) for detail_id in
                         detail_id_set])

                    # 生成件箱出货口和栈板出货口占用情况
                    box_port_arr = ''.join(
                        map(str, solution_bool_c[i][j][k][0][:box_port_num.get(config['deliverFactoryCode'])]))
                    pallet_port_arr = ''.join(
                        map(str, solution_bool_c[i][j][k][0][box_port_num.get(config['deliverFactoryCode']):]))

                    # 更新vehicle_task对象
                    vehicle_task.deliver_factory = config['deliverFactory']
                    vehicle_task.deliver_factory_code = config['deliverFactoryCode']
                    vehicle_task.ems_case_deliver_port_num = box_port_arr
                    vehicle_task.ems_pallet_deliver_port_num = pallet_port_arr
                    vehicle_task_list.append(vehicle_task)

    @staticmethod
    def create_wave_order_sku(detail_data, detail, config, vehicle_task, windows_data, y, is_box=True):
        # 根据detail创建WaveOrderSku对象
        num = detail['box_pcs_num'] if is_box else detail['pallet_pcs_num']
        num_key = 'box_num' if is_box else 'pallet_num'
        pcs_num_key = 'box_pcs_num' if is_box else 'pallet_pcs_num'

        if detail[num_key] == 0:
            return None

        return WaveOrderSku(
            is_urgent=1,
            sku=detail_data.get(detail['detail_id'])[0],
            orderdetil=detail['detail_id'],
            num=num,
            deliver_factory=config['deliverFactory'],
            deliver_factory_code=config['deliverFactoryCode'],
            wops_delivery_time_windowid=windows_data[y]['id'],
            wops_vehicle_taskid=vehicle_task.id,
            pallet_num=0 if is_box else detail[num_key],
            pallet_pcs_num=0 if is_box else detail[pcs_num_key],
            box_num=detail[num_key] if is_box else 0,
            box_pcs_num=detail[pcs_num_key] if is_box else 0,
        )

    @staticmethod
    def get_unload_car_detail(car_list, solution_bool_sub_job, job_list, total_sub_data,
                              total_d_num, no_car_re, unload_detail_list, no_job_data_re):
        # 添加缺车未排的订单详情
        for i, index in enumerate(no_car_re):
            detail_id = total_sub_data[index[0]][index[1]][index[2]]
            found = False

            # 遍历unload_detail_list检查detail_id是否存在
            for item in unload_detail_list:
                if item.get('自制缺车未排订单ID') == detail_id or item.get('未排订单ID') == detail_id:
                    found = True
                    break  # 找到后退出循环

            # 如果detail_id既不在'自制缺车未排订单ID'中，也不在'未排订单ID'中，则添加新项
            if not found:
                unload_detail_list.append({'自制缺车未排订单ID': detail_id, '未排件箱数': no_job_data_re[i][0],
                                           '未排栈板数': no_job_data_re[i][1]})

        # 遍历车辆列表，添加其他未排订单的详情
        for i, dlf in enumerate(car_list):
            for j, car in enumerate(dlf):
                for k, goods in enumerate(car):
                    # 检查是否为未排订单
                    if solution_bool_sub_job[i][j][k][0] == 0:
                        # 添加未排订单的详情
                        unload_detail_list.extend(
                            DataProcessor.get_unload_order_details(job_list[i][j][k], total_sub_data,
                                                                   total_d_num[i][j][k])
                        )

    @staticmethod
    def get_unload_order_details(jobs, total_sub_data, total_d_nums):
        # 初始化一个空字典来存储合并后的详情
        merged_details = {}

        # 遍历jobs列表生成未排订单的详情
        for g, job in enumerate(jobs):
            detail_id = total_sub_data[job[0]][job[1]][job[2]]
            box_num, pallet_num = total_d_nums[g]

            # 如果detail_id已经在字典中，则累加件箱数和栈板数
            if detail_id in merged_details:
                merged_details[detail_id]['未排件箱数'] += box_num
                merged_details[detail_id]['未排栈板数'] += pallet_num
                # 如果detail_id不在字典中，则创建新的条目
            else:
                merged_details[detail_id] = {'未排订单ID': detail_id, '未排件箱数': box_num, '未排栈板数': pallet_num}

                # 将合并后的详情字典转换为列表
        details = list(merged_details.values())

        return details

    @staticmethod
    def split_order_details(job_list, total_d_num, total_d_goods, total_sub_data, lengths, total_sum):
        # 准备详细列表和总计数
        detail_list, total_counts = DataProcessor.prepare_detail_list(job_list, total_d_num, total_d_goods,
                                                                      total_sub_data)
        # 初始化件箱列表和栈板列表
        time_box_list, time_pallet_list = defaultdict(list), defaultdict(list)
        # 初始化累加的件箱和栈板数量及其件数
        box_add_num, pallet_add_num, box_add_pcs_num, pallet_add_pcs_num = 0, 0, 0, 0

        # 遍历每个时间段
        for i, (y, length) in enumerate(lengths.items()):
            # 计算当前时间段的比例
            percentage = length / total_sum
            # 根据比例计算分配的件箱、栈板数量及其件数
            box, pallet, box_pcs, pallet_pcs = DataProcessor.calculate_proportions(percentage, total_counts,
                                                                                   box_add_num, pallet_add_num,
                                                                                   box_add_pcs_num, pallet_add_pcs_num,
                                                                                   i == len(lengths) - 1)

            # 分配订单详情到件箱列表和栈板列表
            DataProcessor.distribute_details(detail_list, time_box_list, time_pallet_list, y, box, pallet, box_pcs,
                                             pallet_pcs)

            # 更新累加的栈板跟件箱数量及其件数
            box_add_num += box
            pallet_add_num += pallet
            box_add_pcs_num += box_pcs
            pallet_add_pcs_num += pallet_pcs

        return time_box_list, time_pallet_list

    @staticmethod
    def prepare_detail_list(job_list, total_d_num, total_d_goods, total_sub_data):
        # 初始化订单详情和总计数
        detail_list = {}
        total_box_num, total_pallet_num, total_box_pcs_num, total_pallet_pcs_num = 0, 0, 0, 0
        # 遍历工作列表，累加件箱跟栈板的数量及其件数
        for g, job in enumerate(job_list):
            box_num, pallet_num = total_d_num[g]
            box_pcs_num, pallet_pcs_num = total_d_goods[g]
            total_box_num += box_num
            total_pallet_num += pallet_num
            total_box_pcs_num += box_pcs_num
            total_pallet_pcs_num += pallet_pcs_num
            detail_id = total_sub_data[job[0]][job[1]][job[2]]
            detail_list[detail_id] = {'box_num': box_num, 'pallet_num': pallet_num, 'box_pcs_num': box_pcs_num,
                                      'pallet_pcs_num': pallet_pcs_num}
        total_counts = (total_box_num, total_pallet_num, total_box_pcs_num, total_pallet_pcs_num)
        return detail_list, total_counts

    @staticmethod
    def calculate_proportions(percentage, total_counts, box_add_num, pallet_add_num, box_add_pcs_num,
                              pallet_add_pcs_num, is_last):
        # 根据比例和是否为最后一个时间段来计算分配的栈板、件箱数量及其件数
        total_box_num, total_pallet_num, total_box_pcs_num, total_pallet_pcs_num = total_counts
        if is_last:
            box = total_box_num - box_add_num
            pallet = total_pallet_num - pallet_add_num
            box_pcs = total_box_pcs_num - box_add_pcs_num
            pallet_pcs = total_pallet_pcs_num - pallet_add_pcs_num
        else:
            box = math.floor(percentage * total_box_num)
            pallet = math.floor(percentage * total_pallet_num)
            box_pcs = 0 if box == 0 else math.floor(percentage * total_box_pcs_num)
            pallet_pcs = 0 if pallet == 0 else math.floor(percentage * total_pallet_pcs_num)
        return box, pallet, box_pcs, pallet_pcs

    @staticmethod
    def distribute_details(detail_list, time_box_list, time_pallet_list, y, box, pallet, box_pcs, pallet_pcs):
        # 分配的栈板、件箱
        for detail_id, item in detail_list.items():
            if box > 0 and item['box_num'] > 0:
                allocated_box = min(box, item['box_num'])
                allocated_box_pcs = (allocated_box / item['box_num']) * item['box_pcs_num']
                # 更新订单详情列表中的箱数
                item['box_num'] -= allocated_box
                if item['box_num'] == 0:
                    allocated_box_pcs = item['box_pcs_num']
                # 更新件箱列表
                time_box_list[y].append(
                    {'detail_id': detail_id, 'box_num': allocated_box, 'box_pcs_num': allocated_box_pcs})
                # 更新订单详情列表中的件数
                item['box_pcs_num'] -= allocated_box_pcs
                # 减少剩余可分配的件箱数量和件数
                box -= allocated_box
                # box_pcs -= allocated_box_pcs

            if pallet > 0 and item['pallet_num'] > 0:
                allocated_pallet = min(pallet, item['pallet_num'])
                allocated_pallet_pcs = item['pallet_pcs_num'] * (allocated_pallet / item['pallet_num'])
                # 更新订单详情列表中的栈板数量
                item['pallet_num'] -= allocated_pallet
                if item['pallet_num'] == 0:
                    allocated_pallet_pcs = item['pallet_pcs_num']
                # 更新栈板列表
                time_pallet_list[y].append(
                    {'detail_id': detail_id, 'pallet_num': allocated_pallet, 'pallet_pcs_num': allocated_pallet_pcs})
                # 更新订单详情列表中的件数
                item['pallet_pcs_num'] -= allocated_pallet_pcs
                # 减少剩余可分配的栈板数量和件数
                pallet -= allocated_pallet
                # pallet_pcs -= allocated_pallet_pcs

    @staticmethod
    def generate_element_to_indices_dict(nested_list):
        result = {}
        for sublist_index, sublist in enumerate(nested_list):
            # 使用列表表示下标
            index = [sublist_index]
            # 直接使用元素作为键，下标列表作为值
            result[sublist[0]] = index
        return result

    @staticmethod
    def get_completed_car(task_service: TaskDataService, ems_task_list, zz_task_list):
        data_list, insert_time, max_seq_id, max_seq_id_state = task_service.get_completed_loading()
        deliver_plan_time = []
        detail_port_num = []
        detail_id_arr = []
        vehicle_taskid_arr = []

        if len(data_list) == 0:
            return (deliver_plan_time, detail_port_num, detail_id_arr,
                    vehicle_taskid_arr, insert_time, max_seq_id, max_seq_id_state)
        for item in data_list:
            detail_port_num.append([int(char) for char in item.ems_case_deliver_port_num]
                                   + [int(char) for char in item.ems_pallet_deliver_port_num])
            port_list = []
            if item.box_begin is not None and item.box_end is not None:
                port_list.append([parse_second(item.box_begin), parse_second(item.box_end)])
            if item.pallet_begin is not None and item.pallet_end is not None:
                port_list.append([parse_second(item.pallet_begin), parse_second(item.pallet_end)])

            deliver_plan_time.append(port_list)
            vehicle_taskid_arr.append(item.wops_vehicle_taskid)

            if item.CONSIGNEEKEY is not None and item.CONSIGNEEKEY != ' ' and item.CONSIGNEEKEY != '':
                story_key = str(item.deliver_factory_code) + str("_") + str(item.CONSIGNEEKEY)
            else:
                story_key = str(item.deliver_factory_code)
            task = ems_task_list.get(story_key)
            if task is not None:
                detail_id_arr.append([task, 0])
            else:
                task = zz_task_list.get(story_key)
                if task is not None:
                    detail_id_arr.append([0, task])
                else:
                    detail_id_arr.append([])
        return (deliver_plan_time, detail_port_num, detail_id_arr,
                list(set(vehicle_taskid_arr)), insert_time, max_seq_id, max_seq_id_state)
